<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  

  
  <title>动态生成简约MVC请求接口|抛弃一切注解减少重复劳动吧 | 地藏思维</title>
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
  <meta name="description" content="背景目前创建一个后端请求接口给别人提供服务，无论是使用SpringMVC方式注解，还是使用SpringCloud的Feign注解，都是需要填写好@RequestMap、@Controller、@Pathvariable等注解和参数。每个接口都需要重复的劳动，非常繁琐。特别是服务治理框架的接口层不是springmvc，而都是通过TCP连接来做RPC通信的接口，这样的接口调试起来比较麻烦，测试人员也不">
<meta property="og:type" content="article">
<meta property="og:title" content="动态生成简约MVC请求接口|抛弃一切注解减少重复劳动吧">
<meta property="og:url" content="https://kelvin-cai.gitee.io/dizang-blog/2020/09/28/%E5%8A%A8%E6%80%81%E7%94%9F%E6%88%90%E7%AE%80%E7%BA%A6MVC%E8%AF%B7%E6%B1%82%E6%8E%A5%E5%8F%A3%E6%8A%9B%E5%BC%83%E4%B8%80%E5%88%87%E6%B3%A8%E8%A7%A3%E5%87%8F%E5%B0%91%E9%87%8D%E5%A4%8D%E5%8A%B3%E5%8A%A8%E5%90%A7/index.html">
<meta property="og:site_name" content="地藏思维">
<meta property="og:description" content="背景目前创建一个后端请求接口给别人提供服务，无论是使用SpringMVC方式注解，还是使用SpringCloud的Feign注解，都是需要填写好@RequestMap、@Controller、@Pathvariable等注解和参数。每个接口都需要重复的劳动，非常繁琐。特别是服务治理框架的接口层不是springmvc，而都是通过TCP连接来做RPC通信的接口，这样的接口调试起来比较麻烦，测试人员也不">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://user-gold-cdn.xitu.io/2020/4/7/1715285196878ac9?w=1240&h=536&f=png&s=152796">
<meta property="og:image" content="https://user-gold-cdn.xitu.io/2020/4/7/17152851c54ddba5?w=962&h=917&f=png&s=50085">
<meta property="og:image" content="https://user-gold-cdn.xitu.io/2020/4/7/17152851f529618b?w=2172&h=800&f=jpeg&s=177409">
<meta property="article:published_time" content="2020-09-28T03:37:06.000Z">
<meta property="article:modified_time" content="2020-09-28T03:10:34.756Z">
<meta property="article:author" content="Kelvin">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://user-gold-cdn.xitu.io/2020/4/7/1715285196878ac9?w=1240&h=536&f=png&s=152796">
  
    <link rel="alternate" href="/dizang-blog/atom.xml" title="地藏思维" type="application/atom+xml">
  
  
    <link rel="icon" href="/favicon.png">
  
  
    <link href="//fonts.googleapis.com/css?family=Source+Code+Pro" rel="stylesheet" type="text/css">
  
  
<link rel="stylesheet" href="/dizang-blog/css/style.css">

<meta name="generator" content="Hexo 5.2.0"></head>

<body>
  <div id="container">
    <div id="wrap">
      <header id="header">
  <div id="banner"></div>
  <div id="header-outer" class="outer">
    <div id="header-title" class="inner">
      <h1 id="logo-wrap">
        <a href="/dizang-blog/" id="logo">地藏思维</a>
      </h1>
      
    </div>
    <div id="header-inner" class="inner">
      <nav id="main-nav">
        <a id="main-nav-toggle" class="nav-icon"></a>
        
          <a class="main-nav-link" href="/dizang-blog/">Home</a>
        
          <a class="main-nav-link" href="/dizang-blog/archives">Archives</a>
        
      </nav>
      <nav id="sub-nav">
        
          <a id="nav-rss-link" class="nav-icon" href="/dizang-blog/atom.xml" title="RSS Feed"></a>
        
        <a id="nav-search-btn" class="nav-icon" title="搜索"></a>
      </nav>
      <div id="search-form-wrap">
        <form action="//google.com/search" method="get" accept-charset="UTF-8" class="search-form"><input type="search" name="q" class="search-form-input" placeholder="Search"><button type="submit" class="search-form-submit">&#xF002;</button><input type="hidden" name="sitesearch" value="https://kelvin-cai.gitee.io/dizang-blog"></form>
      </div>
    </div>
  </div>
</header>
      <div class="outer">
        <section id="main"><article id="post-动态生成简约MVC请求接口抛弃一切注解减少重复劳动吧" class="article article-type-post" itemscope itemprop="blogPost">
  <div class="article-meta">
    <a href="/dizang-blog/2020/09/28/%E5%8A%A8%E6%80%81%E7%94%9F%E6%88%90%E7%AE%80%E7%BA%A6MVC%E8%AF%B7%E6%B1%82%E6%8E%A5%E5%8F%A3%E6%8A%9B%E5%BC%83%E4%B8%80%E5%88%87%E6%B3%A8%E8%A7%A3%E5%87%8F%E5%B0%91%E9%87%8D%E5%A4%8D%E5%8A%B3%E5%8A%A8%E5%90%A7/" class="article-date">
  <time datetime="2020-09-28T03:37:06.000Z" itemprop="datePublished">2020-09-28</time>
</a>
    
  </div>
  <div class="article-inner">
    
    
      <header class="article-header">
        
  
    <h1 class="article-title" itemprop="name">
      动态生成简约MVC请求接口|抛弃一切注解减少重复劳动吧
    </h1>
  

      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <h1 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h1><p>目前创建一个后端请求接口给别人提供服务，无论是使用SpringMVC方式注解，还是使用SpringCloud的Feign注解，都是需要填写好@RequestMap、@Controller、@Pathvariable等注解和参数。每个接口都需要重复的劳动，非常繁琐。特别是服务治理框架的接口层不是springmvc，而都是通过TCP连接来做RPC通信的接口，这样的接口调试起来比较麻烦，测试人员也不能感知接口参数，压力测试的时候没得使用JMETER方便。</p>
<h1 id="目的"><a href="#目的" class="headerlink" title="目的"></a>目的</h1><p>为了解放双手，让后端服务开发人员提供接口给别人时，只需要更关注逻辑。减少开发人员关注框架内容，减少关注每个@注解上的参数信息，不用再校验path是否已经被使用过。无须再感知SpringMVC或者Feign的存在。</p>
<p>我们统一做处理，把类名和方法名来做为请求接口url，不再显式声明url，默认POST请求、返回为JSON形式，请求参数支持@RequestBody、@RequestParam。</p>
<blockquote>
<p>点赞再看，关注公众号：【地藏思维】给大家分享互联网场景设计与架构设计方案<br>掘金：地藏Kelvin <a target="_blank" rel="noopener" href="https://juejin.im/user/1046390801444615">https://juejin.im/user/1046390801444615</a></p>
</blockquote>
<a id="more"></a>

<h1 id="先看看简约到什么程度"><a href="#先看看简约到什么程度" class="headerlink" title="先看看简约到什么程度"></a>先看看简约到什么程度</h1><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Contract</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">UserContract</span> </span>&#123;</span><br><span class="line">       </span><br><span class="line">    <span class="function">User <span class="title">getUserBody</span><span class="params">(User user)</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">UserContractImpl</span> <span class="keyword">implements</span> <span class="title">UserContract</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> User <span class="title">getUserBody</span><span class="params">(User user11)</span> </span>&#123;</span><br><span class="line">        user11.setAge(<span class="number">123</span>);</span><br><span class="line">        <span class="keyword">return</span> user11;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>上述代码已生成的功能：</p>
<ol>
<li>url为 /UserContract/getUserBody的uri，</li>
<li>请求方法为POST</li>
<li>并且请求方式支持body方式提交user11对象</li>
<li>如果参数是基本类型的话默认是作为@RequestParam方式请求</li>
<li>返回方式为JSON</li>
<li>前端同事一说url是啥，就能定位在代码的哪个地方了</li>
</ol>
<p>大家看，是不是不用再填写任何的MVC、Feign注解了！！！！！</p>
<ol>
<li>只需要使用@Contract注解，我们就会生成好一个类下所有方法的POST请求接口，并映射到对应方法。</li>
<li>让开发人员只需要关注请求接口内逻辑，不再需要关注Controller如何生成。<br>代码一个MVC注解都没有，对mvc接口生成无感知。</li>
<li>不嵌入实体类构建Bean过程。</li>
<li>相较正常的@Controller类，少写@RequestMapping 等注解和上面的参数，少写@RequestBody、少写@RequestBody等参数解析方式。这些都不用再显式填写。只需要添加我们自定义注解，并在服务启动时的动态生成简约MVC完成。</li>
</ol>
<h1 id="使用场景"><a href="#使用场景" class="headerlink" title="使用场景"></a>使用场景</h1><p>如果你的请求接口框架通过封装RPC，底层不是springMVC，但又想增添MVC接口。</p>
<p>如果你的请求接口框架通过封装RPC，底层不是springMVC，但又想提供给前端HTML使用。</p>
<p>如果你的请求接口框架通过封装RPC，底层不是springMVC，但又想提供给测试人员方便阅读，也方便用JMETER做压力测试。</p>
<p>如果你的请求接口框架通过封装RPC，底层不是springMVC，想自测接口的时候，没有http接口来做自测，要么写个单元测试每次都启动一下spring来自测整个接口逻辑，这样耗时的情况。</p>
<p>如果你的接口是Feign或者已经是springMVC，但是还在填写url、path、请求method、参数解析方式、每次都要核对ur有没有重复使用等繁琐工作，可以放下这些操作了。</p>
<h1 id="需求"><a href="#需求" class="headerlink" title="需求"></a>需求</h1><ol>
<li>只创建mvc的url与实现类的方法的关联关系，不为实现类创建bean对象入容器，只关注MVC层面，不耦合其他层面的功能。</li>
<li>支持POST请求</li>
<li>类名和方法名拼接成为uri</li>
<li>请求参数支持@RequestParam，@RequestBody</li>
<li>返回数据为JSON</li>
<li>基于springboot</li>
</ol>
<h1 id="前置了解"><a href="#前置了解" class="headerlink" title="前置了解"></a>前置了解</h1><p>Spring的钩子类、钩子方法</p>
<h1 id="Previously"><a href="#Previously" class="headerlink" title="Previously"></a>Previously</h1><p>先看看原生MVC如何绑定URL和方法<br><img src="https://user-gold-cdn.xitu.io/2020/4/7/1715285196878ac9?w=1240&h=536&f=png&s=152796" alt="consice.png"></p>
<p>我们自己的实现主要处理第二步，注入我们自己的RequestMappingHandler。然后做第6、7步重写，让找@Controller的方法改为找@Contract，最后重写处理url生成的方法。</p>
<h1 id="实现"><a href="#实现" class="headerlink" title="实现"></a>实现</h1><h2 id="1-启动方式"><a href="#1-启动方式" class="headerlink" title="1. 启动方式"></a>1. 启动方式</h2><p>首先实现启动方式，使用下述注解放在在Springboot服务启动类上，标明请求接口的实现类代码在哪个路径。然后通过@Import(ContractAutoHandlerRegisterConfiguration.class) 在服务启动时，添加url和类的关联关系。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Retention(RetentionPolicy.RUNTIME)</span></span><br><span class="line"><span class="meta">@Target(ElementType.TYPE)</span></span><br><span class="line"><span class="meta">@Documented</span></span><br><span class="line"><span class="meta">@Import(ContractAutoHandlerRegisterConfiguration.class)</span></span><br><span class="line"><span class="keyword">public</span> <span class="meta">@interface</span> EnableContractConciseMvcRegister &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Contract 注解的请求包扫描路径</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    String[] basePackages() <span class="keyword">default</span> &#123;&#125;;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h2 id="2-import加载负责url和方法关联关系处理的类"><a href="#2-import加载负责url和方法关联关系处理的类" class="headerlink" title="2. import加载负责url和方法关联关系处理的类"></a>2. import加载负责url和方法关联关系处理的类</h2><p>利用ImportBeanDefinitionRegistrar ，就会在@import时触发逻辑，让类BeanDefinition注册到容器中。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ContractAutoHandlerRegisterConfiguration</span> <span class="keyword">implements</span> <span class="title">ImportBeanDefinitionRegistrar</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">registerBeanDefinitions</span><span class="params">(AnnotationMetadata metadata, BeanDefinitionRegistry registry)</span> </span>&#123;</span><br><span class="line">        log.info(<span class="string">&quot;开始注册MVC映射关系&quot;</span>);</span><br><span class="line">        Map&lt;String, Object&gt; defaultAttrs = metadata</span><br><span class="line">                .getAnnotationAttributes(EnableContractConciseMvcRegister.class.getName(), <span class="keyword">true</span>);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (defaultAttrs == <span class="keyword">null</span> ||  !defaultAttrs.containsKey(<span class="string">&quot;basePackages&quot;</span>))</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> IllegalArgumentException(<span class="string">&quot;basePackages not found&quot;</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">//获取扫描包路径</span></span><br><span class="line">        Set&lt;String&gt; basePackages = getBasePackages(metadata);</span><br><span class="line">        <span class="comment">//生成BeanDefinition并注册到容器中</span></span><br><span class="line">        BeanDefinitionBuilder mappingBuilder = BeanDefinitionBuilder</span><br><span class="line">                .genericBeanDefinition(ContractAutoHandlerRegisterHandlerMapping.class);</span><br><span class="line">        mappingBuilder.addConstructorArgValue(basePackages);</span><br><span class="line">        registry.registerBeanDefinition(<span class="string">&quot;contractAutoHandlerRegisterHandlerMapping&quot;</span>, mappingBuilder.getBeanDefinition());</span><br><span class="line"></span><br><span class="line">        BeanDefinitionBuilder processBuilder = BeanDefinitionBuilder.genericBeanDefinition(ContractReturnValueWebMvcConfigurer.class);</span><br><span class="line">        registry.registerBeanDefinition(<span class="string">&quot;contractReturnValueWebMvcConfigurer&quot;</span>, processBuilder.getBeanDefinition());</span><br><span class="line">        log.info(<span class="string">&quot;结束注册MVC映射关系&quot;</span>);</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>
<ol>
<li>利用Import形式registerBeanDefinitions时注入容器。</li>
<li>其中重要的只有ContractAutoHandlerRegisterHandlerMapping，ContractReturnValueWebMvcConfigurer。<br>ContractAutoHandlerRegisterHandlerMapping ，负责url与实现类（如UserContractImpl）方法的关联关系。<br>ContractReturnValueWebMvcConfigurer，处理请求参数解析和方法返回数据转换。</li>
</ol>
<blockquote>
<p>这里利用注解和ImportBeanDefinitionRegistrar 实现了需求6 支持springboot容器。</p>
</blockquote>
<h2 id="3-方法与URL映射"><a href="#3-方法与URL映射" class="headerlink" title="3. 方法与URL映射"></a>3. 方法与URL映射</h2><p>创建ContractAutoHandlerRegisterHandlerMapping继承RequestMappingHandlerMapping。<br>重写几个比较重要的方法，其中一个是isHandler。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;**</span><br><span class="line">     * 判断是否符合触发自定义注解的实现类方法</span><br><span class="line">     *&#x2F;</span><br><span class="line">    @Override</span><br><span class="line">    protected boolean isHandler(Class&lt;?&gt; beanType) &#123;</span><br><span class="line">        &#x2F;&#x2F; 注解了 @Contract 的接口, 并且是这个接口的实现类</span><br><span class="line"></span><br><span class="line">        &#x2F;&#x2F; 传进来的可能是接口，比如 FactoryBean 的逻辑</span><br><span class="line">        if (beanType.isInterface())</span><br><span class="line">            return false;</span><br><span class="line"></span><br><span class="line">        &#x2F;&#x2F; 是否是Contract的代理类，如果是则不支持</span><br><span class="line">        if (ClassUtil.isContractTargetClass(beanType))</span><br><span class="line">            return false;</span><br><span class="line"></span><br><span class="line">        &#x2F;&#x2F; 是否在包范围内，如果不在则不支持</span><br><span class="line">        if (!isPackageInScope(beanType))</span><br><span class="line">            return false;</span><br><span class="line"></span><br><span class="line">        &#x2F;&#x2F; 是否有标注了 @Contract 的接口</span><br><span class="line">        Class&lt;?&gt; contractMarkClass &#x3D; ClassUtil.getContractMarkClass(beanType);</span><br><span class="line">        return contractMarkClass !&#x3D; null;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>
<p>继承这个类重写这个方法的主要原因是</p>
<ol>
<li>经过上面第一步已经把这个关联关系放入容器中后，启动SpringMVC注册时，上述RequestMappingHandlerMapping这个类有继承InitializingBean接口，就是通过这个**<em>InitializingBean的afterPropertiesSet方法**</em>执行后续的逻辑，这个是入口的关键，这个就是告诉等bean都构建完成后初始工作完成后处理的工作方法。（如流程图第5步）</li>
<li>springMVC原生<strong>RequestMappingHandlerMapping</strong>的afterPropertiesSet 这个时候会扫你工程代码里所有类，并且会触发我们自定义的<strong>ContractAutoHandlerRegisterHandlerMapping</strong>上述的<strong>isHandler</strong>方法</li>
<li>这个isHandler方法就需要我们去判断，扫到的这个类是否符合创建mvc接口的类。</li>
<li>我们继承了RequestMappingHandlerMapping，就可以自定义判断的逻辑。判断的逻辑就是这个class字节码是个类，不是interface，并且这个类上面必须有implement了一个interface，而且这个interface需要有@Contract注解（这个类没有贴代码，就是自定义普通的注解，写个名字就好了）</li>
<li>这样就可以标记这是我们需要动态创建简约MVC的类，这个类下的所有方法，都会被创建springMVC请求接口，那些被标记需要创建MVC的类就如前面样例的**<em>UserContractImpl**</em>。</li>
</ol>
<h2 id="3-如何动态创建MVC接口（关键点）"><a href="#3-如何动态创建MVC接口（关键点）" class="headerlink" title="3. 如何动态创建MVC接口（关键点）"></a>3. 如何动态创建MVC接口（关键点）</h2><p>在ContractAutoHandlerRegisterHandlerMapping我们这个自定义类下，重写getMappingForMethod这个方法，这个方法就是用来生成接口的URL，我们要有自己的方式所以要重写。</p>
<p>因为当经过上一节，逻辑找到你代码工程下符合创建简约MVC的类后，如找到UserContractImpl后，ContractAutoHandlerRegisterHandlerMapping的父类RequestMappingHandlerMapping逻辑会去找到UserContractImpl所有方法并进行创建url，然后绑定方法和url关系。（如流程图的第7~9步）</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br></pre></td><td class="code"><pre><span class="line">@Override</span><br><span class="line">    protected RequestMappingInfo getMappingForMethod(Method method, Class&lt;?&gt; handlerType) &#123;</span><br><span class="line">        Class&lt;?&gt; contractMarkClass &#x3D; ClassUtil.getContractMarkClass(handlerType);</span><br><span class="line">        try &#123;</span><br><span class="line">            &#x2F;&#x2F; 查找到原始接口的方法，获取其注解解析为 requestMappingInfo</span><br><span class="line">            Method originalMethod &#x3D; contractMarkClass.getMethod(method.getName(), method.getParameterTypes());</span><br><span class="line">            RequestMappingInfo info &#x3D; buildRequestMappingByMethod(originalMethod);</span><br><span class="line"></span><br><span class="line">            if (info !&#x3D; null) &#123;</span><br><span class="line">                RequestMappingInfo typeInfo &#x3D; buildRequestMappingByClass(contractMarkClass);</span><br><span class="line">                if (typeInfo !&#x3D; null)</span><br><span class="line">                    info &#x3D; typeInfo.combine(info);</span><br><span class="line">            &#125;</span><br><span class="line">            return info;</span><br><span class="line">        &#125; catch (NoSuchMethodException ex) &#123;</span><br><span class="line">            return null;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    private RequestMappingInfo buildRequestMappingByClass(Class&lt;?&gt; contractMarkClass) &#123;</span><br><span class="line"></span><br><span class="line">        String simpleName &#x3D; contractMarkClass.getSimpleName();</span><br><span class="line">        String[] paths &#x3D; new String[] &#123; simpleName &#125;;</span><br><span class="line">        RequestMappingInfo.Builder builder &#x3D; RequestMappingInfo.paths(resolveEmbeddedValuesInPatterns(paths));</span><br><span class="line"></span><br><span class="line">        &#x2F;&#x2F; 通过反射获得 config</span><br><span class="line">        if (!isGetSupperClassConfig) &#123;</span><br><span class="line">            BuilderConfiguration config &#x3D; getConfig();</span><br><span class="line">            this.mappingInfoBuilderConfig &#x3D; config;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        if (this.mappingInfoBuilderConfig !&#x3D; null)</span><br><span class="line">            return builder.options(this.mappingInfoBuilderConfig).build();</span><br><span class="line">        else</span><br><span class="line">            return builder.build();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    private RequestMappingInfo buildRequestMappingByMethod(Method originalMethod) &#123;</span><br><span class="line">        String name &#x3D; originalMethod.getName();</span><br><span class="line">        String[] paths &#x3D; new String[] &#123; name &#125;;</span><br><span class="line">        &#x2F;&#x2F; 用名字作为url</span><br><span class="line">        &#x2F;&#x2F; post形式</span><br><span class="line">        &#x2F;&#x2F; json请求</span><br><span class="line">        RequestMappingInfo.Builder builder &#x3D; RequestMappingInfo.paths(resolveEmbeddedValuesInPatterns(paths))</span><br><span class="line">                .methods(RequestMethod.POST);</span><br><span class="line">&#x2F;&#x2F;                .params(requestMapping.params())</span><br><span class="line">&#x2F;&#x2F;                .headers(requestMapping.headers())</span><br><span class="line">&#x2F;&#x2F;                .consumes(MediaType.APPLICATION_JSON_VALUE)</span><br><span class="line">&#x2F;&#x2F;                .produces(MediaType.APPLICATION_JSON_VALUE)</span><br><span class="line">&#x2F;&#x2F;                .mappingName(name);</span><br><span class="line">        return builder.options(this.getConfig()).build();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    RequestMappingInfo.BuilderConfiguration getConfig() &#123;</span><br><span class="line">        Field field &#x3D; null;</span><br><span class="line">        RequestMappingInfo.BuilderConfiguration configChild &#x3D; null;</span><br><span class="line">        try &#123;</span><br><span class="line">            field &#x3D; RequestMappingHandlerMapping.class.getDeclaredField(&quot;config&quot;);</span><br><span class="line">            field.setAccessible(true);</span><br><span class="line">            configChild &#x3D; (RequestMappingInfo.BuilderConfiguration) field.get(this);</span><br><span class="line">        &#125; catch (IllegalArgumentException | IllegalAccessException e) &#123;</span><br><span class="line">            log.error(e.getMessage(),e);</span><br><span class="line">        &#125; catch (NoSuchFieldException | SecurityException e) &#123;</span><br><span class="line">            log.error(e.getMessage(),e);</span><br><span class="line">        &#125;</span><br><span class="line">        return configChild;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>


<ol>
<li>getMappingForMethod这个方法就是为了，处理实现类UserContractImpl下所有方法的url，得到url后会处理绑定关系到MVC的容器中。后续请求进来了，就会从这个MVC的容器map中根据url为key，找到value，value就是实现类的方法。</li>
<li>getMappingForMethod里的自己定义的buildRequestMappingByClass这个方法就是解析类名，我们的逻辑就是把类名作为接口uri的第一部分。如：/UserContract</li>
<li>自定义的buildRequestMappingByMethod就是处理方法，把方法名作为uri的第二部分，如/getUser。并且在这里设定了为post作为请求方式.<blockquote>
<p>这里完成了需求3：类名和方法名拼接成为uri、需求2 POST请求方式</p>
</blockquote>
</li>
<li>鉴于springmvc请求接口进来时，即使我们接口方法getUser的参数没有注解，都会默认使用@RequestParam通过参数名字来映射，请求接口的参数。</li>
<li>如果是有成员变量的类对象，springmvc也会默认成@RequestBody来处理<blockquote>
<p>这里完成了需求4 请求参数支持@RequestParam，@RequestBody</p>
</blockquote>
</li>
</ol>
<h2 id="4-处理请求接口返回"><a href="#4-处理请求接口返回" class="headerlink" title="4. 处理请求接口返回"></a>4. 处理请求接口返回</h2><p>之前第一步注册的ContractReturnValueWebMvcConfigurer，就是做参数与返回处理。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ContractReturnValueWebMvcConfigurer</span> <span class="keyword">implements</span> <span class="title">BeanFactoryAware</span>, <span class="title">InitializingBean</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> WebMvcConfigurationSupport webMvcConfigurationSupport;</span><br><span class="line">    <span class="keyword">private</span> ConfigurableBeanFactory beanFactory;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setBeanFactory</span><span class="params">(BeanFactory beanFactory)</span> <span class="keyword">throws</span> BeansException </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (beanFactory <span class="keyword">instanceof</span> ConfigurableBeanFactory) &#123;</span><br><span class="line">            <span class="keyword">this</span>.beanFactory = (ConfigurableBeanFactory) beanFactory;</span><br><span class="line">            <span class="keyword">this</span>.webMvcConfigurationSupport = beanFactory.getBean(WebMvcConfigurationSupport.class);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">afterPropertiesSet</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            Class&lt;WebMvcConfigurationSupport&gt; configurationSupportClass = WebMvcConfigurationSupport.class;</span><br><span class="line">            List&lt;HttpMessageConverter&lt;?&gt;&gt; messageConverters = ClassUtil.invokeNoParameterMethod(configurationSupportClass, webMvcConfigurationSupport, <span class="string">&quot;getMessageConverters&quot;</span>);</span><br><span class="line">            List&lt;HandlerMethodReturnValueHandler&gt; returnValueHandlers = ClassUtil.invokeNoParameterMethod(configurationSupportClass, webMvcConfigurationSupport, <span class="string">&quot;getReturnValueHandlers&quot;</span>);</span><br><span class="line">            List&lt;HandlerMethodArgumentResolver&gt; argumentResolverHandlers = ClassUtil.invokeNoParameterMethod(configurationSupportClass, webMvcConfigurationSupport, <span class="string">&quot;getArgumentResolvers&quot;</span>);</span><br><span class="line"></span><br><span class="line">            <span class="comment">//只要匹配@Contract的方法，并将所有返回值都当作 @ResponseBody 注解进行处理  </span></span><br><span class="line">            returnValueHandlers.add(<span class="keyword">new</span> ContractRequestResponseBodyMethodProcessor(messageConverters));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>利用InitializingBean把WebMvcConfigurationSupport拿出来。对有自定义注解@Contract的interface的方法才会有特殊处理，这些方法都会使用@ResponseBody返回，就不用再在实现类的方法写@ResponseBody了</p>
<blockquote>
<p>这里完成需求4 支持@ResponseBody</p>
</blockquote>
<h1 id="使用与测试"><a href="#使用与测试" class="headerlink" title="使用与测试"></a>使用与测试</h1><ol>
<li>前面样例的UserContractImpl已经写了，只需要注意在UserContractImpl的interface（UserContract）上填@Contract。请求接口的代码类就不重复贴了。</li>
<li>现在编写springboot启动类，注意basePackages 为请求接口的实现类的包路径。<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@EnableAutoConfiguration</span> </span><br><span class="line"><span class="meta">@ComponentScan</span></span><br><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="meta">@EnableContractConciseMvcRegister(basePackages = &quot;com.dizang.concise.mvc.controller.impl&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ConsicesMvcApplication</span> </span>&#123;</span><br><span class="line">    </span><br><span class="line">	<span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">		SpringApplication.run(ConsicesMvcApplication.class, args);</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li>
<li>启动后，打开swagger-ui.html<br><img src="https://user-gold-cdn.xitu.io/2020/4/7/17152851c54ddba5?w=962&h=917&f=png&s=50085" alt="image.png"></li>
</ol>
<h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>到目前为止，我们没有在工程代码中使用springmvc注解，也能生成接口映射关系了。<br>这样大家以后就再也不用写SpringMVC的注解也能使用SpringMVC了，如果你公司框架默认是tcp连接的RPC接口，只要使用了这种方式，就可以自己本地调试，不用再编写一个RPC客户端来访问自己的接口。使用Swagger调试又比较方便，而且测试同时也能看到请求参数，也可以对其做JMETER压力测试。<br>不过代码都有一个问题，就是做法越统一，约束就越多。想自由，就约束少。所以我们这个框架，就只能用POST请求，并且ResponseBody来返回，就不适合要跳转重定向页面的那种，也不支持@PathVariable的参数解析方式，没那么RestFul风格（但可以把GET POST方式更改为用int值放在请求参数里），但是支持@RequestParam和@RequestBody形式，我觉得也是足够了。</p>
<h1 id="代码样例"><a href="#代码样例" class="headerlink" title="代码样例"></a>代码样例</h1><p><a target="_blank" rel="noopener" href="https://gitee.com/kelvin-cai/concise-mvc-register">https://gitee.com/kelvin-cai/concise-mvc-register</a></p>
<hr>
<h1 id="欢迎关注公众号，文章更快一步"><a href="#欢迎关注公众号，文章更快一步" class="headerlink" title="欢迎关注公众号，文章更快一步"></a>欢迎关注公众号，文章更快一步</h1><p>我的公众号 ：地藏思维</p>
<p><img src="https://user-gold-cdn.xitu.io/2020/4/7/17152851f529618b?w=2172&h=800&f=jpeg&s=177409"></p>
<p>掘金：地藏Kelvin</p>
<p>简书：地藏Kelvin</p>
<p>我的Gitee： 地藏Kelvin <a target="_blank" rel="noopener" href="https://gitee.com/kelvin-cai">https://gitee.com/kelvin-cai</a></p>

      
    </div>
    <footer class="article-footer">
      <a data-url="https://kelvin-cai.gitee.io/dizang-blog/2020/09/28/%E5%8A%A8%E6%80%81%E7%94%9F%E6%88%90%E7%AE%80%E7%BA%A6MVC%E8%AF%B7%E6%B1%82%E6%8E%A5%E5%8F%A3%E6%8A%9B%E5%BC%83%E4%B8%80%E5%88%87%E6%B3%A8%E8%A7%A3%E5%87%8F%E5%B0%91%E9%87%8D%E5%A4%8D%E5%8A%B3%E5%8A%A8%E5%90%A7/" data-id="ckfnc6n4800026coodgl833d5" class="article-share-link">Share</a>
      
      
    </footer>
  </div>
  
    
<nav id="article-nav">
  
    <a href="/dizang-blog/2020/09/29/%E3%80%8ASpringCloudDubbo%E5%BC%80%E5%8F%91%E6%97%A5%E8%AE%B0%E3%80%8B%EF%BC%88%E4%B8%80%EF%BC%89Nacos%E8%BF%9E%E5%AE%98%E6%96%B9%E6%96%87%E6%A1%A3%E9%83%BD%E6%B2%A1%E5%86%99%E5%A5%BD/" id="article-nav-newer" class="article-nav-link-wrap">
      <strong class="article-nav-caption">Newer</strong>
      <div class="article-nav-title">
        
          《SpringCloudDubbo开发日记》（一）Nacos连官方文档都没写好
        
      </div>
    </a>
  
  
    <a href="/dizang-blog/2020/09/28/%E4%B8%BB%E9%94%AE%E7%94%9F%E6%88%90%E5%99%A8%E6%95%88%E7%8E%87%E6%8F%90%E5%8D%87%E6%96%B9%E6%A1%88%E5%9F%BA%E4%BA%8E%E9%9B%AA%E8%8A%B1%E7%AE%97%E6%B3%95%E5%92%8CRedis%E6%8E%A7%E5%88%B6%E8%BF%9B%E7%A8%8B%E9%9A%94%E7%A6%BB/" id="article-nav-older" class="article-nav-link-wrap">
      <strong class="article-nav-caption">Older</strong>
      <div class="article-nav-title">主键生成器效率提升方案|基于雪花算法和Redis控制进程隔离</div>
    </a>
  
</nav>

  
</article>

</section>
        
          <aside id="sidebar">
  
    

  
    

  
    
  
    
  <div class="widget-wrap">
    <h3 class="widget-title">归档</h3>
    <div class="widget">
      <ul class="archive-list"><li class="archive-list-item"><a class="archive-list-link" href="/dizang-blog/archives/2020/09/">九月 2020</a></li></ul>
    </div>
  </div>


  
    
  <div class="widget-wrap">
    <h3 class="widget-title">最新文章</h3>
    <div class="widget">
      <ul>
        
          <li>
            <a href="/dizang-blog/2020/09/29/%E3%80%8ASpringCloudDubbo%E5%BC%80%E5%8F%91%E6%97%A5%E8%AE%B0%E3%80%8B%EF%BC%88%E4%B8%80%EF%BC%89Nacos%E8%BF%9E%E5%AE%98%E6%96%B9%E6%96%87%E6%A1%A3%E9%83%BD%E6%B2%A1%E5%86%99%E5%A5%BD/">《SpringCloudDubbo开发日记》（一）Nacos连官方文档都没写好</a>
          </li>
        
          <li>
            <a href="/dizang-blog/2020/09/28/%E5%8A%A8%E6%80%81%E7%94%9F%E6%88%90%E7%AE%80%E7%BA%A6MVC%E8%AF%B7%E6%B1%82%E6%8E%A5%E5%8F%A3%E6%8A%9B%E5%BC%83%E4%B8%80%E5%88%87%E6%B3%A8%E8%A7%A3%E5%87%8F%E5%B0%91%E9%87%8D%E5%A4%8D%E5%8A%B3%E5%8A%A8%E5%90%A7/">动态生成简约MVC请求接口|抛弃一切注解减少重复劳动吧</a>
          </li>
        
          <li>
            <a href="/dizang-blog/2020/09/28/%E4%B8%BB%E9%94%AE%E7%94%9F%E6%88%90%E5%99%A8%E6%95%88%E7%8E%87%E6%8F%90%E5%8D%87%E6%96%B9%E6%A1%88%E5%9F%BA%E4%BA%8E%E9%9B%AA%E8%8A%B1%E7%AE%97%E6%B3%95%E5%92%8CRedis%E6%8E%A7%E5%88%B6%E8%BF%9B%E7%A8%8B%E9%9A%94%E7%A6%BB/">主键生成器效率提升方案|基于雪花算法和Redis控制进程隔离</a>
          </li>
        
      </ul>
    </div>
  </div>

  
</aside>
        
      </div>
      <footer id="footer">
  
  <div class="outer">
    <div id="footer-info" class="inner">
      &copy; 2020 Kelvin<br>
      Powered by <a href="http://hexo.io/" target="_blank">Hexo</a>
    </div>
  </div>
</footer>
    </div>
    <nav id="mobile-nav">
  
    <a href="/dizang-blog/" class="mobile-nav-link">Home</a>
  
    <a href="/dizang-blog/archives" class="mobile-nav-link">Archives</a>
  
</nav>
    

<script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>


  
<link rel="stylesheet" href="/dizang-blog/fancybox/jquery.fancybox.css">

  
<script src="/dizang-blog/fancybox/jquery.fancybox.pack.js"></script>




<script src="/dizang-blog/js/script.js"></script>




  </div>
</body>
</html>